package com.minibobbo.utilities.spe;

import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Array;
import com.minibobbo.utilities.extrectangle.eRectangle;
import com.minibobbo.utilities.spe.actionmodules.SpeActionModule;
import com.minibobbo.utilities.spe.aimodules.SpeAIModule;
import com.minibobbo.utilities.spe.controlmodule.SpeControlModule;
import com.minibobbo.utilities.spe.effectmodules.SpeEffectModule;
import com.minibobbo.utilities.spe.interactions.SpeInteraction;
import com.minibobbo.utilities.spe.movementmodules.SpeMovementModule;
import com.minibobbo.utilities.spe.physicsmodules.SpePhysicsModule;
import com.minibobbo.utilities.spe.sensemodules.SpeSenseModule;

public class SpeDynamicObject extends SpeObject {

	public enum MovementType {
		momentum, explicit
	}

	// The location variables
	public eRectangle destination;
	public Vector2 changeVector;
	public Vector2 physicsVector;
	public Vector2 facing;
	

	// Module variables
	public SpePhysicsModule physicsModule;
	public SpeMovementModule moveModule;
	public SpeControlModule controlModule;
	public SpeAIModule aiModule;
	public SpeSenseModule sModule;
	public Array<SpeInteraction> interactions;
	public Array<SpeActionModule> actions;

	
	// Draw and sleep variables
	public boolean drawMe = true;
	public boolean sleeping = false;
	public boolean allowContacts = true;

	Array<SpeValueModule> values;
	Array<SpeDynamicObject> objectsSensed;

	// Touch variables
	public boolean tL, tR, tU, tD;
	
	

	// Stun variables
	private boolean stunned = false;
	private float stunTime;

	// The engine that should be used.
	SimplePlatformEngine engine;

	// Should this object be disposed this update?
	private boolean disposeMe;

	// Control variables
	public boolean u, d, l, r;
	public boolean[] b;

	// Collision variables
	int collisionIteration;

	public SpeDynamicObject() {
		initObjectVariables();
		SpeObjType = SpeObjectType.dynamicObj;
//		loc = new eRectangle();
		interactions = new Array<SpeInteraction>();
		destination = new eRectangle();
		changeVector = new Vector2();
		physicsVector = new Vector2();
		facing = new Vector2(1,0);
		objectsSensed = new Array<SpeDynamicObject>();
		b = new boolean[8];
		values = new Array<SpeValueModule>();
		actions = new Array<SpeActionModule>();

	}


	public void setBounds(float x, float y, float width, float height) {
		loc.x = x;
		loc.y = y;
		loc.width = width;
		loc.height = height;
		destination.set(loc);
		setInitialVoxelInfo();

	}

	/**
	 * Resets all the movement variables to be false.
	 */
	public void resetMovementVariables() {
		u = false;
		d = false;
		l = false;
		r = false;
	}

	/**
	 * Resets all the button variables to be false.
	 */
	public void resetButtonVariables() {
		for (int i = 0; i < b.length; i++) {
			b[i] = false;
		}
	}

	public void setEngine(SimplePlatformEngine engine) {
		this.engine = engine;
	}

	public void update(float delta) {
		// Check if we are stunned.
		if (stunned) {
			stunTime -= delta;
			if (stunTime <= 0)
				stunned = false;
		}

		// Run through all the modules assigned to this object.
		if (sModule != null)
			sModule.updateSenses(delta);
		if (aiModule != null)
			aiModule.updateAI(delta);
		if (controlModule != null && !stunned)
			controlModule.updateControl(delta);
		if (moveModule != null && !stunned)
			moveModule.updateMovement(delta);
		if (physicsModule != null)
			physicsModule.updatePhysics(delta);
		for(SpeActionModule aMod: actions)
			aMod.takeAction(delta);
		

		// Checks if we changed what voxels we should be in. Now the destination
		// rect is where we will end up.
		if (allowContacts)
			updateVoxelInfo();

		updatePositionToDestination();
	}

	private int vX, vY;

	public int getvX() {
		return vX;
	}

	public int getvY() {
		return vY;
	}

	public void setInitialVoxelInfo() {
		int vX = (int) (loc.getCenter().x / engine.voxelArea);
		int vY = (int) (loc.getCenter().y / engine.voxelArea);
		this.vX = vX;
		this.vY = vY;

		for (int x = vX - 1; x <= vX + 1; x++) {
			for (int y = vY - 1; y <= vY + 1; y++)
				engine.addToVoxel(this, x, y);
		}

	}

	private void removeFromVoxels() {
		int vX = (int) (loc.getCenter().x / engine.voxelArea);
		int vY = (int) (loc.getCenter().y / engine.voxelArea);
		this.vX = vX;
		this.vY = vY;

		for (int x = vX - 1; x <= vX + 1; x++) {
			for (int y = vY - 1; y <= vY + 1; y++)
				engine.removeFromVoxel(this, x, y);

		}

	}

	private void updateVoxelInfo() {
		int vX1 = (int) (destination.getCenter().x / engine.voxelArea);
		int vX0 = (int) (loc.getCenter().x / engine.voxelArea);
		int vY1 = (int) (destination.getCenter().y / engine.voxelArea);
		int vY0 = (int) (loc.getCenter().y / engine.voxelArea);

		// If we have moved a voxel
		// if(vX0!=vX1 || vY0 != vY1) {
		// engine.removeFromVoxel(this, vX0, vY0);
		// engine.addToVoxel(this, vX1, vY1);
		// }

		if (vX0 > vX1)
			voxelLeft(vX1, vY1);
		if (vX0 < vX1)
			voxelRight(vX1, vY1);
		if (vY0 > vY1)
			voxelDown(vX1, vY1);
		if (vY0 < vY1)
			voxelUp(vX1, vY1);

	}

	private void voxelDown(int vX, int vY) {
		engine.addToVoxel(this, vX - 1, vY - 1);
		engine.addToVoxel(this, vX, vY - 1);
		engine.addToVoxel(this, vX + 1, vY - 1);
		engine.removeFromVoxel(this, vX - 1, vY + 2);
		engine.removeFromVoxel(this, vX, vY + 2);
		engine.removeFromVoxel(this, vX + 1, vY + 2);
		this.vX = vX;
		this.vY = vY;

	}

	private void voxelUp(int vX, int vY) {
		engine.addToVoxel(this, vX - 1, vY + 1);
		engine.addToVoxel(this, vX, vY + 1);
		engine.addToVoxel(this, vX + 1, vY + 1);
		engine.removeFromVoxel(this, vX - 1, vY - 2);
		engine.removeFromVoxel(this, vX, vY - 2);
		engine.removeFromVoxel(this, vX + 1, vY - 2);
		this.vX = vX;
		this.vY = vY;

	}

	private void voxelLeft(int vX, int vY) {
		engine.addToVoxel(this, vX - 1, vY - 1);
		engine.addToVoxel(this, vX - 1, vY);
		engine.addToVoxel(this, vX - 1, vY + 1);
		engine.removeFromVoxel(this, vX + 2, vY + 1);
		engine.removeFromVoxel(this, vX + 2, vY);
		engine.removeFromVoxel(this, vX + 2, vY - 1);
		this.vX = vX;
		this.vY = vY;

	}

	private void voxelRight(int vX, int vY) {
		engine.addToVoxel(this, vX + 1, vY - 1);
		engine.addToVoxel(this, vX + 1, vY);
		engine.addToVoxel(this, vX + 1, vY + 1);
		engine.removeFromVoxel(this, vX - 2, vY + 1);
		engine.removeFromVoxel(this, vX - 2, vY);
		engine.removeFromVoxel(this, vX - 2, vY - 1);
		this.vX = vX;
		this.vY = vY;

	}

	public void updatePositionToDestination() {
		loc.set(destination);
	}

	/**
	 * Disposes of all the assets for this object. Also, removes it from the
	 * engine.
	 */
	public void dispose() {
		disposeMe = true;
		removeFromVoxels();

	}

	public boolean isDisposed() {
		return disposeMe;
	}

	/**
	 * Applies a force to the dynamic body.
	 * 
	 * @param x
	 *            The X value.
	 * @param y
	 *            The Y value.
	 */
	public void applyForce(float x, float y) {
		changeVector.x += x;
		physicsVector.y += y;
	}

	/**
	 * Sets the object's lower left corner position in the world. Note that this
	 * ignores physics and can result in strange behaviors.
	 * 
	 * @param x
	 *            X location in world units.
	 * @param y
	 *            Y location in world units.
	 */
	public void setPosition(float x, float y) {
		loc.x = x;
		loc.y = y;
	}

	/**
	 * Adds a physics module to this dynamic object. The physics module affects
	 * how the object will interact with the world. This replaces any existing
	 * physics module on the object.
	 * 
	 * @param pMod
	 *            The physics module to add to the object.
	 */
	public SpePhysicsModule addPhysicsModule(SpePhysicsModule pMod) {
		physicsModule = pMod;
		physicsModule.init(engine, this);
		return pMod;
	}

	/**
	 * Adds a movement module to this dynamic object. The movement module
	 * affects how this object will move through the physical environment
	 * 
	 * @param mMod
	 */
	public SpeMovementModule addMovementModule(SpeMovementModule mMod) {
		moveModule = mMod;
		moveModule.init(engine, this);
		return mMod;
	}

	public SpeControlModule addControlModule(SpeControlModule cMod) {
		controlModule = cMod;
		controlModule.init(engine, this);
		return cMod;
	}

	public SpeAIModule addAIModule(SpeAIModule aiMod) {
		aiModule = aiMod;
		aiModule.init(engine, this);
		return aiMod;
	}
	
	public SpeInteraction addInteraction(SpeInteraction i) {
		i.init(engine, this);
		interactions.add(i);
		return i;
	}
	
	public SpeActionModule addActionModule(SpeActionModule aMod) {
		aMod.init(engine, this);
		actions.add(aMod);
		return aMod;
	}
	


	public void resetTouches() {
		tL = false;
		tR = false;
		tU = false;
		tD = false;
	}

	public void contact(SpeDynamicObject obj) {
		for(SpeInteraction interaction: interactions) {
			interaction.interact(obj);
			
		}
	}
	

	/**
	 * Returns the value. If it doesn't exist, returns null
	 * 
	 * @param name
	 *            The name of the value to return
	 * @return The value. Null if it doesn't exist.
	 */
	public SpeValueModule getValue(String name) {
		for (SpeValueModule v : values) {
			if (v.name == name)
				return v;
		}
		return null;
	}

	/**
	 * Is this button pressed?
	 * 
	 * @param button
	 *            Button to check
	 * @return True if pressed. Otherwise false.
	 */
	public boolean isButtonPressed(int button) {
		if (button < 0 || button >= b.length)
			return false;
		return b[button];
	}



	/**
	 * Sets the stunned flag o this object for a set amount of time. Note that
	 * this restricts the movement modules, although the object is still
	 * affected by physics.
	 * 
	 * @param stunTime
	 *            How long the stunned flag should remain set.
	 */
	public void stunMe(float stunTime) {
		this.stunTime = stunTime;
		stunned = true;
	}

	public float getMaxSpeedX() {
		if (moveModule != null)
			return moveModule.maxSpeedX;
		else
			return 0;
	}

	public float getMaxSpeedY() {
		if (moveModule != null)
			return moveModule.maxSpeedY;
		else
			return 0;
	}

}
